import { index2char, char2index } from "./constants.js";

/**
 * @param {number} num
 * @returns {string} base64 string
 */
export function encode_num(num) {
  // normalize
  if (num < 0) {
    num = (-num << 1) | 1;
  } else {
    num = num << 1;
  }

  let result = "";

  do {
    let part = num & 0b11_111; // get last 5 bits
    num = num >>> 5; // go next 5 bits
    if (num > 0) {
      // continuation
      part = 0b100_000 | part; // add continuation bit
    }

    result = result + index2char(part);
  } while (num > 0);

  return result;
}

/**
 * @param {number[]} nums
 * @returns {string} base64 string
 */
export function encode_nums(nums) {
  let result = "";

  for (const num of nums) {
    result += encode_num(num);
  }

  return result;
}

/**
 * @param {string} str - base64 string
 * @return {number[]}
 */
export function decode(str) {
  let result = [];

  let value = 0;
  let shift = 0;
  for (const char of str) {
    const int = char2index(char);

    const group = int & 0b11_111;
    value = (group << shift) | value;

    const continuation = int & 0b100_000;

    if (continuation) {
      // next 5 bits
      shift += 5;
    } else {
      // end
      // handle negative
      const neg = value & 1;
      if (neg) {
        value = -(value >>> 1);
        // prevent overflow
        // 0x8000_0000 is the max negative number
        // 0x7fff_ffff is the max positive number
        // when we shift the raw num `<< 1`, we lose the highest 1 bit.
        // and we got 0, in fact it's -0x8000_0000.
        if (value === 0) value = -0x8000_0000;
      } else {
        value >>>= 1;
      }

      result.push(value);

      // reset for next number
      value = shift = 0;
    }
  }

  return result;
}

/**
 * @param {number | number[]} num
 * @returns {string} base64 string
 */
export function encode(num) {
  if (Array.isArray(num)) {
    return encode_nums(num);
  }

  return encode_num(num);
}
